home *** CD-ROM | disk | FTP | other *** search
/ Macwelt 1 / Macwelt DVD 1.toast / Web-Publishing / HTML-Editoren / Alpha ƒ / Tcl / SystemCode / CorePackages / filesets.tcl < prev    next >
Encoding:
Text File  |  2001-02-07  |  34.2 KB  |  1,168 lines

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  AlphaTcl - core Tcl engine
  4.  # 
  5.  #    FILE: "filesets.tcl"
  6.  #                    created: 20/7/96 {6:22:25 pm} 
  7.  #                   last update: 2/7/2001 {9:20:49 PM} 
  8.  #    Author:    Vince Darley
  9.  #    E-mail:    <vince@santafe.edu>
  10.  #      mail:    317 Paseo de Peralta, Santa Fe, NM 87501, USA
  11.  #       www:    <http://www.santafe.edu/~vince/>
  12.  #    
  13.  #==============================================================================
  14.  # Alpha calls two fileset-related routines, 'getCurrFileSet', and 
  15.  # 'getFileSetNames'. Alpha will also attempt to set the variable 'currFileSet'
  16.  # on occasion, but this isn't critical.
  17.  #==============================================================================
  18.  # 
  19.  # This file, and the interfaces it contains are undergoing some
  20.  # development.  The APIs may undergo minor changes in the future,
  21.  # as we learn more about how users want to interact with filesets.
  22.  # Code contributions and suggestions are very welcome.
  23.  # ###################################################################
  24.  ##
  25.  
  26. alpha::extension filesets 1.0fc1 {
  27.     # Something in here must ensure this file is sourced.
  28.     
  29.     # Build some filesets on the fly.
  30.     set gfileSets(Help) [list [file join $HOME Help *] 3]
  31.     # Declare their types
  32.     set gfileSetsType(Help) "fromHierarchy"
  33.     filesetRegisterProcedural "Open Windows" procFilesetOpenWindows
  34.     filesetRegisterProcedural "Top Window's Folder" procFilesetDirTopWin
  35.     filesetRegisterProcedural "Top Window's Hierarchy" procFilesetHierarchyTopWin
  36.     filesetRegisterProcedural "Recurse in folder…" procFilesetRecurseIn
  37.     lunion varPrefs(Files) currFileSet
  38.     # The current fileset is used as a default for some actions.  It may
  39.     # also be updated automatically to reflect the user's most recent
  40.     # fileset-menu selection.
  41.     newPref var currFileSet "Top Window's Folder" global changeFileSet gfileSets array
  42.  
  43.     ## 
  44.      # A type is a means of prompting the user and characterising
  45.      # the interface to a type, even though the actual storage may be
  46.      # very simple (a list in most cases).
  47.      ##
  48.     fileset::registerNewType fromDirectory "glob"
  49.     fileset::registerNewType fromHierarchy "fromHierarchy"
  50.     fileset::registerNewType fromOpenWindows "list"
  51.     fileset::registerNewType procedural "procedural"
  52.     fileset::registerNewType recurseIn "procedural"
  53.  
  54.     set fileset::notChangeable [list "Open Windows" \
  55.       "Top Window's Folder" "Top Window's Hierarchy" \
  56.       "Recurse in folder…"]
  57.  
  58.     prefs::modified currFileSet
  59.  
  60.     hook::register preOpeningHook fileset::checkOpeningPreference
  61.     
  62.     # Make sure our preferences are ok.
  63.     foreach fset [array names gfileSets] {
  64.     if {![info exists gfileSetsType($fset)]} {
  65.         lappend fsetErrors $fset
  66.         unset gfileSets($fset)
  67.         prefs::modified gfileSets($fset)
  68.     }
  69.     }
  70.     if {[info exists fsetErrors]} {
  71.     alertnote "[join $fsetErrors {, }] filesets were corrupted, and have\
  72.       been removed"
  73.     unset fsetErrors
  74.     }
  75.     if {[info exists fset]} {unset fset}
  76. } maintainer {
  77.     "Vince Darley" vince@santafe.edu <http://www.santafe.edu/~vince/>
  78. } help {file "Filesets Help"}
  79.  
  80. # Register utilities
  81.  
  82. set    "filesetUtils(browseFileset…)" [list * browseFileset]
  83. set    "filesetUtils(renameFileset…)" [list * renameFileset]
  84. set    "filesetUtils(openEntireFileset…)" [list * openEntireFileset]
  85. set    "filesetUtils(filesetToAlpha…)" [list * filesetToAlpha]
  86. set    "filesetUtils(closeEntireFileset…)" [list * closeEntireFileset]
  87. set    "filesetUtils(replaceInFileset…)" [list * replaceInFileset]
  88. set    "filesetUtils(stuffFileset…)" [list * stuffFileset]
  89. set    "filesetUtils(wordCount)" [list * wordCountFileset]
  90. set    "filesetUtils(openFilesetFolder…)" [list * openFilesetFolder]
  91.  
  92. # ◊◊◊◊ Filesets API ◊◊◊◊ #
  93.  
  94. # A fileset has a few key properties:
  95. # (i) a name
  96. # (ii) a way of testing whether any given file is in that fileset
  97. # (iii) a way of listing all files in the fileset
  98. # (iv) a 'basic type', which is currently any of: 
  99. # 'list'
  100. # 'procedural'
  101. # 'glob'
  102. # 'fromHierarchy'
  103. # It may also have any number of other properties, usually added by
  104. # other packages.  For instance, the filesets menu will add a flag
  105. # declaring whether a fileset is shown in the menu.
  106.  
  107. ## 
  108.  # To add a new fileset type, you need to define the following:
  109.  #       fileset::registerNewType myType "list"
  110.  #       proc    fileset::myType::create {} {}
  111.  #       proc    fileset::myType::updateContents {name {andMenu 0}} {}
  112.  # 
  113.  # For more complex types (e.g. the tex-type), also define:
  114.  #       proc    fileset::myType::selected {fset menu item } {}
  115.  #       proc    fileset::myType::listFiles {name} {}
  116.  # 
  117.  # For filesets you want to make easily editable via the 'editFilesets'
  118.  # dialog, you must also define:
  119.  #       proc    fileset::myType::getDialogItems {name} {}
  120.  #       proc    fileset::myType::setDetails {name args} {}
  121.  # 
  122.  # These procedures will all be called automatically under the correct
  123.  # circumstances.  The purposes of these are as follows:
  124.  #
  125.  #   'create'   -- query the user for name etc. and create
  126.  #   'updateContents'   -- given the information in 'gfileSets', recalculate
  127.  #                   the member files.
  128.  #   'selected' -- a member was selected in a menu.
  129.  #   'listFiles'     -- given info in all except 'fileSets', return list
  130.  #                 of files to be stored in that variable.
  131.  #   'makeFileSetAndMenu'  -- generate the sub-menu
  132.  # 
  133.  # Your code may wish to call 'isWindowInFileset ?win?  ?type?'  to
  134.  # check if a given (current by default) window is in a fileset of a
  135.  # given type.
  136.  ##
  137.  
  138.  
  139. namespace eval fileset {}
  140.  
  141. ## 
  142.  # -------------------------------------------------------------------------
  143.  # 
  144.  # "fileset::registerNewType" --
  145.  # 
  146.  #  Add a new type of fileset to the list of known types.  Having
  147.  #  called this procedure, Alpha will automatically know how to interact
  148.  #  with the new fileset type, provided it fulfills the fileset API.
  149.  #  
  150.  #  This requires the existence of the procs:
  151.  #  
  152.  #    'fileset::$type::create'
  153.  #    'fileset::$type::updateContents'
  154.  #    
  155.  #  Note that the namespace 'fileset::$type' is automatically created
  156.  #  by this procedure.
  157.  #  
  158.  #  Filesets which you want to make editable in the 'Edit Filesets' 
  159.  #  dialog must also define the procs
  160.  #  
  161.  #       proc    fileset::$type::getDialogItems {name}
  162.  #       proc    fileset::$type::setDetails {name args}
  163.  #  
  164.  # -------------------------------------------------------------------------
  165.  ##
  166. proc fileset::registerNewType {type kind} {
  167.     global fileset::typeKindMap
  168.     set fileset::typeKindMap($type) $kind
  169.     # Make sure this namespace exists
  170.     namespace eval ::fileset::$type {}
  171. }
  172.  
  173. ## 
  174.  # -------------------------------------------------------------------------
  175.  # 
  176.  # "fileset::attachNewInformation" --
  177.  # 
  178.  #  If 'to' is '*' then this item is automatically attached to all filesets,
  179.  #  if not then each fileset may individually select whether to attach
  180.  #  this information or not.  This selection is done by the user, via the
  181.  #  'Attach/Detach info' button in the 'Edit A Fileset' dialog.
  182.  # -------------------------------------------------------------------------
  183.  ##
  184. proc fileset::attachNewInformation {to infoType name {defaultValue ""} {help ""} {modifiedScript ""}} {
  185.     global fileset::infoTypes gfileSets fileset::infoStorage
  186.     set fileset::infoTypes($name) [list $to $name $infoType $defaultValue $help $modifiedScript]
  187.     foreach fset [array names gfileSets] {
  188.     if {![info exists fileset::infoStorage($fset,$name)]} {
  189.         set fileset::infoStorage($fset,$name) $defaultValue
  190.         if {[string length $modifiedScript]} {
  191.         eval $modifiedScript [list $fset $defaultValue]
  192.         }
  193.     }
  194.     }
  195. }
  196.  
  197. proc fileset::ensureAllInfoAttached {fsets infoNames} {
  198.     global fileset::infoTypes gfileSets fileset::infoStorage
  199.     if {![llength $fsets]} { set fsets [array names gfileSets] }
  200.     if {![llength $infoNames]} { set infoNames [array names fileset::infoTypes] }
  201.     foreach infoName $infoNames {
  202.     set defaultValue [lindex [set fileset::infoTypes($infoName)] 3]
  203.     set modifiedScript [lindex [set fileset::infoTypes($infoName)] 5]
  204.     foreach fset $fsets {
  205.         if {![info exists fileset::infoStorage($fset,$infoName)]} {
  206.         set fileset::infoStorage($fset,$infoName) $defaultValue
  207.         if {[string length $modifiedScript]} {
  208.             eval $modifiedScript [list $fset $defaultValue]
  209.         }
  210.         }
  211.     }
  212.     }
  213. }
  214.  
  215. proc fileset::isAttached {fset name} {
  216.     global fileset::attachments
  217.     if {[info exists fileset::attachments($fset,$name)]} {
  218.     return [set fileset::attachments($fset,$name)]
  219.     } else {
  220.     return 0
  221.     }
  222. }
  223.  
  224. proc fileset::chooseAttachments {fset} {
  225.     global fileset::infoTypes
  226.     set items {}
  227.     foreach name [array names fileset::infoTypes] {
  228.     set val [set fileset::infoTypes($name)]
  229.     if {[lindex $val 0] == "*"} {
  230.         # always attached
  231.         continue
  232.     } else {
  233.         lappend items $name [fileset::isAttached $fset $name]
  234.     }
  235.     }
  236.     if {![llength $items]} {
  237.     alertnote "No items exist which can be attached"
  238.     return
  239.     }
  240.     set x 10
  241.     set y 10
  242.     eval lappend dialog [dialog::text "Checked items are currently attached" 10 y]
  243.     incr y 10
  244.     newforeach {name val} $items {
  245.     eval lappend dialog [dialog::checkbox $name $val 10 y]
  246.     }
  247.     incr y 10
  248.     set dialog [concat [dialog::okcancel 10 y] $dialog]
  249.     set res [eval [list dialog -w 360 -h $y] $dialog]
  250.     if {[lindex $res 1]} {
  251.     return "Cancelled"
  252.     }
  253.     set count 2
  254.     global fileset::attachments
  255.     set mod 0
  256.     newforeach {name val} $items {
  257.     set newval [lindex $res $count]
  258.     if {$newval != $val} {
  259.         set mod 1
  260.         if {$newval} {
  261.         set fileset::attachments($fset,$name) 1
  262.         } else {
  263.         unset fileset::attachments($fset,$name)
  264.         }
  265.         prefs::modified fileset::attachments($fset,$name)
  266.     }
  267.     incr count
  268.     }
  269.     if {$mod} {
  270.     alertnote "The next time you use the 'Edit Filesets' or \
  271.       'Edit A Fileset' dialog, these changes will take effect."
  272.     }
  273.     return "Done"
  274. }
  275.  
  276. proc fileset::informationAttached {fset} {
  277.     global fileset::infoTypes
  278.     set res {}
  279.     foreach name [array names fileset::infoTypes] {
  280.     set val [set fileset::infoTypes($name)]
  281.     if {[lindex $val 0] == "*" || [fileset::isAttached $fset $name]} {
  282.         lappend res [lrange $val 1 end]
  283.     }
  284.     }
  285.     return $res
  286. }
  287.  
  288. ## 
  289.  # -------------------------------------------------------------------------
  290.  # 
  291.  # "fileset::attachAdditionalInformation" --
  292.  # 
  293.  #  Attach a piece of information which is only shown to the user if
  294.  #  the fileset's $toInfoName has the current value $toVal.  Note,
  295.  #  however, that the information is actually attached to all filesets,
  296.  #  it is only the visibility to the user that is adjusted.
  297.  # -------------------------------------------------------------------------
  298.  ##
  299. proc fileset::attachAdditionalInformation {toInfoName toVal infoType name {defaultValue ""} {help ""} {modifiedScript ""}} {
  300.     global fileset::infoAdditionalTypes gfileSets fileset::infoStorage
  301.     lappend fileset::infoAdditionalTypes($toInfoName,$toVal) [list $name $infoType $defaultValue $help $modifiedScript]
  302.     foreach fset [array names gfileSets] {
  303.     if {![info exists fileset::infoStorage($fset,$name)]} {
  304.         set fileset::infoStorage($fset,$name) $defaultValue
  305.         if {[string length $modifiedScript]} {
  306.         eval $modifiedScript [list $fset $defaultValue]
  307.         }
  308.     }
  309.     }
  310. }
  311.  
  312. proc fileset::additionalInformationAttached {fset infoName val} {
  313.     global fileset::infoAdditionalTypes
  314.     if {[info exists fileset::infoAdditionalTypes($infoName,$val)]} {
  315.     return [set fileset::infoAdditionalTypes($infoName,$val)]
  316.     }
  317.     return ""
  318. }
  319.  
  320. proc fileset::makeInfoDialogItems {fset yy} {
  321.     upvar $yy y
  322.     set res {}
  323.     foreach pair [fileset::informationAttached $fset] {
  324.     set name [lindex $pair 0]
  325.     set infoType [lindex $pair 1]
  326.     set val [fileset::getInformation $fset $name]
  327.     eval lappend res [dialog::makeItem $infoType 20 y $fset $name $val]
  328.     }
  329.     set res
  330. }
  331.  
  332. proc fileset::getInfoDialogItems {fset} {
  333.     set res {}
  334.     foreach pair [fileset::informationAttached $fset] {
  335.     set name [lindex $pair 0]
  336.     set infoType [lindex $pair 1]
  337.     set val [fileset::getInformation $fset $name]
  338.     lappend res [list $infoType $name $val [lindex $pair 3]]
  339.     foreach additionalPair [fileset::additionalInformationAttached $fset $name $val] {
  340.         set name [lindex $additionalPair 0]
  341.         set infoType [lindex $additionalPair 1]
  342.         set val [fileset::getInformation $fset $name]
  343.         lappend res [list $infoType $name $val [lindex $additionalPair 3]]
  344.     }
  345.     }
  346.     set res
  347. }
  348.  
  349. proc fileset::setInfoFromDialog {fset count res} {
  350.     foreach pair [fileset::informationAttached $fset] {
  351.     set name [lindex $pair 0]
  352.     set infoType [lindex $pair 1]
  353.     set oldVal [fileset::getInformation $fset $name]
  354.     set val [lindex $res $count]
  355.     fileset::setInformation $fset $name $val
  356.     incr count
  357.     foreach additionalPair [fileset::additionalInformationAttached $fset $name $oldVal] {
  358.         set name [lindex $additionalPair 0]
  359.         set infoType [lindex $additionalPair 1]
  360.         set val [lindex $res $count]
  361.         fileset::setInformation $fset $name $val
  362.         incr count
  363.     }
  364.     }
  365.     return $count
  366. }
  367.  
  368. ## 
  369.  # -------------------------------------------------------------------------
  370.  # 
  371.  # "fileset::listTypes" --
  372.  # 
  373.  #  Return sorted list of all fileset types currently registered.  This
  374.  #  is used, for example, when creating a new fileset, so that we may
  375.  #  ask the user to select the type of fileset they wish to create.
  376.  # -------------------------------------------------------------------------
  377.  ##
  378. proc fileset::listTypes {} {
  379.     global fileset::typeKindMap
  380.     lsort -ignore [array names fileset::typeKindMap]
  381. }
  382.  
  383. proc fileset::getKind {type} {
  384.     global fileset::typeKindMap
  385.     set fileset::typeKindMap($type)
  386. }
  387.  
  388. proc fileset::getKindFromFset {fset} {
  389.     global gfileSetsType fileset::typeKindMap
  390.     set fileset::typeKindMap($gfileSetsType($fset))
  391. }
  392.  
  393. proc fileset::getInformation {fset infoName} {
  394.     global fileset::infoStorage
  395.     if {![info exists fileset::infoStorage($fset,$infoName)]} {
  396.     # This should only happen if we create a fileset manually
  397.     # in Tcl by filling in the various array entries.
  398.     fileset::ensureAllInfoAttached [list $fset] [list]
  399.     }
  400.     set fileset::infoStorage($fset,$infoName)
  401. }
  402.  
  403. proc fileset::setInformation {fset infoName value} {
  404.     global fileset::infoStorage fileset::infoTypes
  405.     if {[info exists fileset::infoStorage($fset,$infoName)]} {
  406.     if {[set fileset::infoStorage($fset,$infoName)] != $value} {
  407.         set fileset::infoStorage($fset,$infoName) $value
  408.         prefs::modified fileset::infoStorage($fset,$infoName)
  409.         # This only fails for 'additional information'
  410.         if {[info exists fileset::infoTypes($infoName)]} {
  411.         set modifiedScript [lindex [set fileset::infoTypes($infoName)] 5]
  412.         if {[string length $modifiedScript]} {
  413.             eval $modifiedScript [list $fset $value]
  414.         }
  415.         }
  416.     }
  417.     } else {
  418.     set fileset::infoStorage($fset,$infoName) $value
  419.     }
  420. }
  421.  
  422. proc fileset::checkOpeningPreference {name} {
  423.     if {[hook::anythingRegistered fileset-file-opening]} {
  424.     set fset [fileset::findForFile $name]
  425.     if {[string length $fset]} {
  426.         hook::callAll fileset-file-opening * $fset $name
  427.     }
  428.     }
  429. }
  430.  
  431. proc fileset::checkCurrent {{win ""}} {
  432.     return [fileset::findForFile $win]
  433. }
  434.  
  435. proc fileset::relativePath {{win ""}} {
  436.     set fset [fileset::findForFile $win]
  437.     global gfileSets
  438.     set root [file dirname $gfileSets($fset)]
  439.     if {[file::pathStartsWith $win $root relative]} {
  440.     return $relative
  441.     } else {
  442.     error "Not relative"
  443.     }
  444. }
  445.  
  446. proc fileset::canEdit {fset} {
  447.     global gfileSetsType
  448.     set type $gfileSetsType($fset)
  449.     if {$type == "procedural"} {
  450.     return -1
  451.     }
  452.     if {[info commands fileset::${type}::getDialogItems] != ""} {
  453.     return 1
  454.     } else {
  455.     return [auto_load fileset::${type}::getDialogItems]
  456.     }
  457. }
  458.  
  459. proc filesetRegisterProcedural {name proc} {
  460.     global gfileSets gfileSetsType
  461.     set gfileSets($name) $proc
  462.     set gfileSetsType($name) "procedural"
  463. }
  464.  
  465.  
  466. # ◊◊◊◊ Basic procedures ◊◊◊◊ #
  467.  
  468. proc editFilesets {} {
  469.     global currFileSet gfileSetsType gfileSets 
  470.     
  471.     # Used to build up the items in the dialog
  472.     set dialog {}
  473.     # Used to store the original values of each fileset entry
  474.     set values {}
  475.     # Create a page for each editable fileset
  476.     foreach fset [lsort -ignore [array names gfileSets]] {
  477.     set canEdit [fileset::canEdit $fset]
  478.     if {$canEdit < 0} {continue}
  479.     set page [list $fset]
  480.     if {$canEdit} {
  481.         set thisfset_items [fileset::$gfileSetsType($fset)::getDialogItems $fset]
  482.         eval lappend page $thisfset_items
  483.         # Store a list of the current values
  484.         set vals [list]
  485.         foreach item $thisfset_items {
  486.         lappend vals [lindex $item 2]
  487.         }
  488.         lappend values $vals 
  489.     }
  490.     eval lappend page [fileset::getInfoDialogItems $fset]
  491.     lappend dialog $page
  492.     }
  493.     set res [eval [list dialog::make -title "Edit filesets" -defaultpage $currFileSet] $dialog]
  494.     # Now set everything
  495.     set stored_index 0
  496.     foreach fset [lsort -ignore [array names gfileSets]] {
  497.     set canEdit [fileset::canEdit $fset]
  498.     if {$canEdit < 0} {continue}
  499.     set count 0
  500.     if {$canEdit} {
  501.         set vals [lindex $values $stored_index]
  502.         set count [llength $vals]
  503.         set mod 0
  504.         for {set i 0} {$i < $count} {incr i} {
  505.         if {[lindex $vals $i] != [lindex $res $i]} {
  506.             set mod 1
  507.             break
  508.         }
  509.         }
  510.         # Only call the 'setDetails' proc if the fileset has changed.
  511.         if {$mod} {
  512.         message "Updating fileset $fset"
  513.         eval fileset::$gfileSetsType($fset)::setDetails [list $fset] [lrange $res 0 [expr {$count -1}]]
  514.         updateAFileset $fset
  515.         }
  516.         incr stored_index
  517.     }
  518.     set count [fileset::setInfoFromDialog $fset $count $res]
  519.     set res [lrange $res $count end]
  520.     }
  521.     message "Fileset changes complete"
  522. }
  523.  
  524. proc editAFileset {{fset ""}} {
  525.     if {[catch {pickFileset $fset "Edit which fileset?" editable} fset]} {return}
  526.     global currFileSet gfileSetsType gfileSets 
  527.     # Used to build up the items in the dialog
  528.     set dialog {}
  529.     # Used to store the original values of each fileset entry
  530.     set values {}
  531.     set page [list $fset]
  532.     set thisfset_items [fileset::$gfileSetsType($fset)::getDialogItems $fset]
  533.     eval lappend page $thisfset_items
  534.     # Store a list of the current values
  535.     set vals [list]
  536.     foreach item $thisfset_items {
  537.     lappend vals [lindex $item 2]
  538.     }
  539.     lappend values $vals 
  540.     eval lappend page [fileset::getInfoDialogItems $fset]
  541.     lappend dialog $page
  542.     set res [eval [list dialog::make -title "Edit '$fset' fileset" \
  543.       -addbuttons [list "Attach/detach info" "Click here to attach or detach optional\
  544.       additional information to this fileset" [list fileset::chooseAttachments $fset]]] $dialog]
  545.     # Now set everything
  546.     set stored_index 0
  547.     set count 0
  548.     set vals [lindex $values $stored_index]
  549.     set count [llength $vals]
  550.     set mod 0
  551.     for {set i 0} {$i < $count} {incr i} {
  552.     if {[lindex $vals $i] != [lindex $res $i]} {
  553.         set mod 1
  554.         break
  555.     }
  556.     }
  557.     # Only call the 'setDetails' proc if the fileset has changed.
  558.     if {$mod} {
  559.     message "Updating fileset $fset"
  560.     eval fileset::$gfileSetsType($fset)::setDetails [list $fset] [lrange $res 0 [expr {$count -1}]]
  561.     updateAFileset $fset
  562.     message "Fileset $fset updated"
  563.     }
  564.     incr stored_index
  565.     set count [fileset::setInfoFromDialog $fset $count $res]
  566.     set res [lrange $res $count end]
  567. }
  568.  
  569. proc newFileset {{type ""}} {
  570.     global currFileSet gfileSetsType
  571.     if {$type == ""} {
  572.     foreach ty [fileset::listTypes] {
  573.         lappend types [quote::Prettify $ty]
  574.     }
  575.     set type [dialog::optionMenu "New fileset type?" \
  576.       $types "From Directory"]
  577.     foreach ty [fileset::listTypes] {
  578.         if {[quote::Prettify $ty] == $type} {
  579.         set type $ty
  580.         break
  581.         }
  582.     }
  583.     }
  584.     set name [eval fileset::${type}::create]
  585.  
  586.     if {![string length $name]} return
  587.     
  588.     set gfileSetsType($name) $type
  589.     prefs::modified gfileSets($name)
  590.     
  591.     set currFileSet $name
  592.     hook::callAll fileset-new $gfileSetsType($name) $name
  593.     return $currFileSet
  594. }
  595.  
  596. proc deleteFileset {{fset ""} {yes 0}} {
  597.     global currFileSet 
  598.     
  599.     if {[catch {pickFileset $fset "Delete which Fileset?" "notbuiltin"} fset]} {
  600.     return
  601.     }
  602.     if {$fset == ""} {
  603.     message "The existing filesets cannot be deleted."
  604.     return
  605.     }
  606.  
  607.     global fileSets gfileSets fileSetsExtra gfileSetsType
  608.  
  609.     if {$yes || [dialog::yesno "Delete fileset \"$fset\"?"]} {
  610.     set type $gfileSetsType($fset)
  611.  
  612.     hook::callAll fileset-delete $type $fset
  613.  
  614.     fileset::uncache $fset
  615.     catch {unset "fileSetsExtra($fset)"}
  616.     catch {unset "gfileSetsType($fset)"}
  617.     catch {unset "fileSets($fset)"}
  618.     catch {unset "gfileSets($fset)"}
  619.     
  620.     # fileSets($fset) is stored not in the usual prefs location,
  621.     # but rather in the fsMenu2.0 cache, so we don't need to
  622.     # call prefs::modified with it.
  623.     prefs::modified gfileSetsType($fset) gfileSets($fset) \
  624.       fileSetsExtra($fset)
  625.     message "The fileset \"$fset\" has been deleted"
  626.  
  627.     if {$currFileSet == $fset} {
  628.         set old $currFileSet
  629.         catch {
  630.         set currFileSet [lindex [array names gfileSets] 0]
  631.         }
  632.         hook::callAll fileset-current * $old $currFileSet
  633.     }
  634.     }
  635. }
  636.  
  637. proc renameFileset {} {
  638.     global fileSets gfileSets currFileSet fileSetsExtra gfileSetsType
  639.     
  640.     if {[catch {pickFileset "" "Fileset to rename?" "notbuiltin"} fset]} {return}
  641.     if {$fset == ""} {
  642.     message "The existing filesets cannot be renamed."
  643.     return
  644.     }
  645.  
  646.     set name [getline "Rename to:" $fset]
  647.     if {![string length $name] || $name == $fset} return
  648.     
  649.     set gfileSets($name) $gfileSets($fset)
  650.     set gfileSetsType($name) $gfileSetsType($fset)
  651.     prefs::modified gfileSets($name) gfileSetsType($name)
  652.  
  653.     if {[info exists fileSets($fset)]} {
  654.     set fileSets($name) $fileSets($fset)
  655.     }
  656.     if {[info exists fileSetsExtra($fset)]} {
  657.     set fileSetsExtra($name) $fileSetsExtra($fset)
  658.     prefs::modified fileSetsExtra($name)
  659.     }
  660.     
  661.     deleteFileset $fset 1
  662.     
  663.     set currFileSet $name
  664.     hook::callAll fileset-new $gfileSetsType($name) $name
  665.     message "The fileset \"$fset\" has been renamed to \"$name\""
  666. }
  667.  
  668. proc updateCurrentFileset {} {
  669.     global currFileSet
  670.     updateAFileset $currFileSet
  671. }
  672.  
  673. proc updateAFileset { {fset ""} } {
  674.     global gfileSetsType
  675.  
  676.     if {[catch {pickFileset $fset} fset]} {return}
  677.     
  678.     fileset::uncache $fset
  679.     fileset::make $fset 1
  680. }
  681.  
  682. proc fileset::uncache {fset} {
  683.     global fsMenuCache
  684.     if {[info exists fsMenuCache($fset)]} {
  685.     unset fsMenuCache($fset)
  686.     cache::add fsMenu2.0 "eval" [list unset fsMenuCache($fset)]
  687.     }
  688.     hook::callAll fileset-uncache * $fset
  689. }
  690.  
  691. proc getFilesInSet {fset} {
  692.     global gfileSets gfileSetsType
  693.     switch -- [fileset::getKind $gfileSetsType($fset)] {
  694.     "list" {
  695.         return $gfileSets($fset)
  696.     }
  697.     "glob" {
  698.         global filesetmodeVars fileSetsExtra
  699.         if {$filesetmodeVars(includeNonTextFiles)} {
  700.         set l [glob -nocomplain -dir [file dirname "$gfileSets($fset)"] -- [file tail "$gfileSets($fset)"]]
  701.         if {[info exists fileSetsExtra($fset)]} {
  702.             foreach pat $fileSetsExtra($fset) {
  703.             foreach f [glob -nocomplain -dir [file dirname "$gfileSets($fset)"] -- $pat] {
  704.                 set i [lsearch $l $f]
  705.                 set l [lreplace $l $i $i]
  706.             }
  707.             }
  708.         }
  709.         return $l
  710.         } else {
  711.         set l [glob -types TEXT -nocomplain -dir [file dirname "$gfileSets($fset)"] -- [file tail "$gfileSets($fset)"]]
  712.         if {[info exists fileSetsExtra($fset)]} {
  713.             foreach pat $fileSetsExtra($fset) {
  714.             foreach f [glob -types TEXT -nocomplain -dir [file dirname "$gfileSets($fset)"] -- $pat] {
  715.                 set i [lsearch $l $f]
  716.                 set l [lreplace $l $i $i]
  717.             }
  718.             }
  719.         }
  720.         return $l
  721.         }
  722.     }
  723.     "procedural" {
  724.         switch -- $gfileSetsType($fset) {
  725.         "recurseIn" {
  726.             return [file::recurse [file dirname $gfileSets($fset)]]
  727.         }
  728.         default {
  729.             return [$gfileSets($fset)]
  730.         }
  731.         }
  732.     }        
  733.     "default" {
  734.         global fileSets
  735.         return $fileSets($fset)
  736.     }
  737.     }
  738. }
  739.  
  740. proc fileset::make {name andMenu} {
  741.     if {$andMenu} {
  742.     global fsMenuCache
  743.     if {[info exists fsMenuCache($name)]} {
  744.         set m [set fsMenuCache($name)]
  745.         if {[llength $m]} { return $m }
  746.     }
  747.     }
  748.  
  749.     global gfileSetsType fileSets
  750.     if {[info exists gfileSetsType($name)]} {
  751.     set type $gfileSetsType($name)
  752.     message "Building ${name}…"
  753.     if {![catch {fileset::${type}::updateContents $name $andMenu} m]} {
  754.         if {[llength $m]} {
  755.         fileset::cacheMenu $name $m
  756.         }
  757.         if {[info exists fileSets($name)]} {
  758.         cache::add fsMenu2.0 "variable" fileSets($name)
  759.         }
  760.         hook::callAll fileset-update $type $name $m
  761.         message "Building ${name}… complete"
  762.         return $m
  763.     } else {
  764.         # nothing
  765.     }
  766.     }
  767.     return [list]
  768. }
  769.  
  770. proc fileset::cacheMenu {fset m} {
  771.     if {[llength $m]} {
  772.     global fsMenuCache
  773.     set fsMenuCache($fset) $m
  774.     cache::add fsMenu2.0 variable fsMenuCache($fset)
  775.     }
  776. }
  777.  
  778. # Called in response to user changing filesets manually
  779. proc changeFileSet {item} {
  780.     global currFileSet tagFile
  781.     if {$currFileSet != $item} {
  782.     set old $currFileSet
  783.     set currFileSet $item
  784.     hook::callAll fileset-current * $old $currFileSet
  785.     }
  786.     # Bring in the tags file for this fileset
  787.     set fname [tagFileName]
  788.     if {[file exists $fname]} {
  789.     if {[dialog::yesno "Use tag file from folder \"$dir\" ?"]} {
  790.         set tagFile $fname
  791.     }
  792.     }
  793. }
  794.  
  795. # ◊◊◊◊ Open an item in a fileset ◊◊◊◊ #
  796.  
  797. proc fileset::openItemProc {fset parent item} {
  798.     global gfileSetsType 
  799.     if {$fset != ""} {set m $fset} else { set m $parent}
  800.     # try a type-specific method first
  801.     set proc fileset::$gfileSetsType($m)::selected
  802.     if {[info commands $proc] == "" && (![auto_load $proc])} {
  803.     # if that failed then just hope it's an ordinary list
  804.     if {![catch {filesetBasicOpen $m $item} err]} {return}
  805.     } else {
  806.     if {[llength [info args $proc]] == 2} {
  807.         if {![catch {eval [list $proc $parent $item]} err]} {return}
  808.     } else {
  809.         if {![catch {eval [list $proc $fset $parent $item]} err]} {return}
  810.     }
  811.     }
  812.     
  813.     fileset::fileNotFound $fset $err
  814. }
  815.  
  816. proc fileset::fileNotFound {fset {text ""}} {
  817.     if {[string length $text]} {
  818.     append text "\r"
  819.     }
  820.     append text "That file wasn't found. The fileset may be out of date."
  821.     if {![catch {dialog::yesno -y "Rebuild fileset" \
  822.       -n "Edit fileset" -c $text} res]} {
  823.     if {$res} {
  824.         updateAFileset $fset
  825.     } else {
  826.         editAFileset $fset
  827.     }
  828.     return 1
  829.     } else {
  830.     message "Cancelled"
  831.     return 0
  832.     }
  833. }
  834.  
  835. proc filesetBasicOpen {fset item} {
  836.     set f [file::pathEndsWith $item [getFilesInSet $fset]]
  837.     if {[string length $f]} {
  838.     autoUpdateFileset $fset
  839.     file::openAny $f
  840.     return
  841.     }
  842.     error "File for selected '$item' not found"
  843. }
  844.  
  845. proc autoUpdateFileset { name } {
  846.     global currFileSet filesetmodeVars
  847.     if {$filesetmodeVars(autoAdjustFileset)} {
  848.     changeFileSet $name
  849.     }
  850. }
  851.  
  852. # ◊◊◊◊ Query procs ◊◊◊◊ #
  853.  
  854. proc fileset::findForFile { {win ""} } {
  855.     if {$win == ""} { set win [win::Current] }
  856.     global currFileSet gfileSets gfileSetsType
  857.     foreach fset [concat [list $currFileSet] [array names gfileSets]] {
  858.     switch -- [fileset::getKind $gfileSetsType($fset)] {
  859.         "list" {
  860.         if {[lsearch -exact $gfileSets($fset) $win] != -1} {
  861.             return $fset
  862.         }
  863.         }
  864.         "glob" {
  865.         if {[file::pathStartsWith $win [file dirname $gfileSets($fset)]]} {
  866.             return $fset
  867.         }
  868.         }
  869.         "procedural" {
  870.         switch -- $gfileSetsType($fset) {
  871.             "recurseIn" {
  872.             if {[file::pathStartsWith $win [file dirname $gfileSets($fset)]]} {
  873.                 return $fset
  874.             }
  875.             }
  876.             default {
  877.             continue
  878.             }
  879.         }
  880.         }
  881.         "fromHierarchy" {
  882.         if {[file::pathStartsWith $win [file dirname [lindex $gfileSets($fset) 0]]]} {
  883.             return $fset
  884.         }
  885.         }
  886.         "default" {
  887.         # Not sure what this is for in this procedure.
  888.         global fileSets
  889.         if {[lsearch -exact $fileSets($fset) $win] != -1} {
  890.             return $fset
  891.         }
  892.         }
  893.     }
  894.     }
  895.     return ""
  896. }
  897.  
  898. proc dirtyFileset { fset } {
  899.     foreach f [getFilesInSet $fset] {
  900.     if { ![catch {getWinInfo -w $f arr}] && $arr(dirty)} { return 1 }
  901.     }
  902.     return 0
  903. }
  904.  
  905. proc isWindowInFileset { {win "" } {type ""} } {
  906.     if {$win == ""} { set win [win::Current] }
  907.     global currFileSet gfileSets gfileSetsType
  908.     
  909.     if { $type == "" } {
  910.     set okSets [array names gfileSets]
  911.     } else {
  912.     set okSets {}
  913.     foreach s [array names gfileSets] {
  914.         if { $gfileSetsType($s) == $type } {
  915.         lappend okSets $s
  916.         }
  917.     }
  918.     }
  919.     
  920.     if {[array exists gfileSets]} {
  921.     if {[lsearch -exact $okSets $currFileSet] != -1 } {
  922.         # check current fileset
  923.         if {[isWindowInFilelist $win [getFilesInSet $currFileSet]]} {
  924.         # we're set, it's in this fileset
  925.         return  $currFileSet
  926.         }
  927.     }
  928.     
  929.     # check other fileset
  930.     foreach fset $okSets {
  931.         if {[isWindowInFilelist $win [getFilesInSet $fset]]} {
  932.         # we're set, it's in this project
  933.         return  $fset
  934.         }
  935.     }   
  936.     }
  937.     return ""
  938.     
  939. }
  940.  
  941. if {[info tclversion] < 8.0} {
  942.     proc isWindowInFilelist { win flist } {
  943.     set win [win::StripCount $win]
  944.     if {[lsearch -exact $flist $win] != -1 } {
  945.         return 1
  946.     } else {
  947.         return 0
  948.     }
  949.     }
  950. } else {
  951.     proc isWindowInFilelist { win flist } {
  952.     set win [win::StripCount $win]
  953.     foreach f $flist {
  954.         if {[string equal $win [file::ensureStandardPath $f]]} {
  955.         return 1
  956.         }
  957.     }
  958.     return 0
  959.     }
  960. }
  961.  
  962. ## 
  963.  # -------------------------------------------------------------------------
  964.  #     
  965.  #    "pickFileset" --
  966.  #    
  967.  # Ask the user for a/several filesets.  If 'fset' is set, we just return
  968.  # that (this avoids 'if {$fset != ""} { set fset [pick...]  } constructs
  969.  # everywhere).  A prompt can be given, and a dialog type (either a
  970.  # listpick, a pop-up menu, or a listpick with multiple selection), and
  971.  # extra items can be added to the list if desired. 
  972.  # -------------------------------------------------------------------------
  973.  ##
  974. proc pickFileset { fset {prompt Fileset?} {type "list"} {extras {}} } {
  975.     global gfileSets currFileSet
  976.     if {[array size gfileSets] == 0} {
  977.     error "There are no filesets currently defined"
  978.     }
  979.     if { $fset != "" } { return $fset }
  980.     switch -- $type {
  981.     "popup" {
  982.         set fset [eval [list prompt $prompt \
  983.           $currFileSet "FileSet:"] [lsort -ignore [array names gfileSets]]]
  984.         if {![info exists gfileSets($fset)]} { error "No such fileset" }
  985.         return $fset
  986.     }
  987.     "list" {
  988.         return [listpick -p $prompt -L $currFileSet \
  989.           [lsort -ignore [concat $extras [array names gfileSets]]]]
  990.     }
  991.     "multilist" {
  992.         return [listpick -p $prompt -l -L $currFileSet \
  993.           [lsort -ignore [concat $extras [array names gfileSets]]]]
  994.     }
  995.     "notbuiltin" {
  996.         global fileset::notChangeable
  997.         set choices [list]
  998.         foreach fset [lsort -ignore [array names gfileSets]] {
  999.         if {[lsearch -exact ${fileset::notChangeable} $fset] == -1} {
  1000.             lappend choices $fset
  1001.         }
  1002.         }
  1003.         if {[llength $choices]} {
  1004.         set item $currFileSet
  1005.         if {[lsearch -exact $choices $currFileSet] == -1} {
  1006.             set item [lindex $choices 0]
  1007.         }
  1008.         return [listpick -p $prompt -L $item \
  1009.           [lsort -ignore [concat $extras $choices]]]
  1010.         } else {
  1011.         return ""
  1012.         }
  1013.     }
  1014.     "editable" {
  1015.         set choices [list]
  1016.         foreach fset [lsort -ignore [array names gfileSets]] {
  1017.         set canEdit [fileset::canEdit $fset]
  1018.         if {$canEdit < 0} {continue}
  1019.         lappend choices $fset
  1020.         }
  1021.         if {[llength $choices]} {
  1022.         set item $currFileSet
  1023.         if {[lsearch -exact $choices $currFileSet] == -1} {
  1024.             set item [lindex $choices 0]
  1025.         }
  1026.         return [listpick -p $prompt -L $item \
  1027.           [lsort -ignore [concat $extras $choices]]]
  1028.         } else {
  1029.         return ""
  1030.         }
  1031.     }
  1032.     }
  1033. }
  1034.  
  1035. # ◊◊◊◊ Tags ◊◊◊◊ #
  1036.  
  1037. if {![string length [info commands alphaFindTag]]} {
  1038.     rename findTag alphaFindTag
  1039.     rename createTagFile alphaCreateTagFile
  1040. }
  1041.  
  1042. proc tagFileName {} {
  1043.     global gfileSets currFileSet 
  1044.     return [file join [file dirname [lindex $gfileSets($currFileSet) 0]] "[join ${currFileSet}]TAGS"]
  1045. }
  1046.  
  1047. proc findTag {} {
  1048.     global gfileSetsType currFileSet
  1049.     # try a type-specific method first
  1050.     if {[catch {fileset::$gfileSetsType($currFileSet)::findTag}]} {
  1051.     alphaFindTag
  1052.     }
  1053. }
  1054.  
  1055. proc createTagFile {} {
  1056.     global gfileSetsType currFileSet tagFile modifiedVars
  1057.     set tagFile [tagFileName]
  1058.     lappend modifiedVars tagFile
  1059.     
  1060.     # try a type-specific method first
  1061.     if {[catch {fileset::$gfileSetsType($currFileSet)::createTagFile}]} {
  1062.     alphaCreateTagFile
  1063.     }
  1064. }
  1065.  
  1066. # ◊◊◊◊ Called by Alpha's core ◊◊◊◊ #
  1067.  
  1068. # Called from Alpha to get list of files for current file set.
  1069. proc getCurrFileSet {} {
  1070.     global currFileSet
  1071.     return [getFileSet $currFileSet]
  1072. }
  1073.  
  1074. # Called from Alpha to get names.  The first name returned is taken to
  1075. # be the current fileset.  For Alpha < 8.0, the list returned contains
  1076. # the first item twice (as the first item, and then in its correct 
  1077. # position in the list).  For Alpha >= 8.0 this silly behaviour has 
  1078. # been removed.
  1079. proc getFileSetNames {{ordered 0}} {
  1080.     global gfileSets currFileSet gDirScan
  1081.     set perm {}
  1082.     if {!$ordered && $currFileSet != ""} {
  1083.     lappend perm $currFileSet
  1084.     }
  1085.     foreach n [lsort -ignore [array names gfileSets]] {
  1086.     if {!$ordered && ([info tclversion] >= 8.0) && $n == $currFileSet} {continue}
  1087.     if {[info exists gDirScan($n)]} {
  1088.         lappend temp $n
  1089.     } else {
  1090.         lappend perm $n
  1091.     }
  1092.     }
  1093.     if {[info exists temp]} {
  1094.     return [concat $perm - $temp]
  1095.     } else {
  1096.     return $perm
  1097.     }
  1098. }
  1099.  
  1100. #================================================================================
  1101. # Edit a file from a fileset via list dialogs (no mousing around).
  1102. #================================================================================
  1103.  
  1104. namespace eval file {} 
  1105.  
  1106. proc file::openViaFileset {{fset ""}} {
  1107.     global currFileSet gfileSetsType file::separator
  1108.     
  1109.     if {[catch {pickFileset $fset {Fileset?} "list"} fset]} {return}
  1110.     set currFileSet $fset
  1111.     
  1112.     if {[info tclversion] < 8.0} {
  1113.     set cmd fileset::$gfileSetsType($fset)::getRoot
  1114.     } else {
  1115.     set cmd ::fileset::$gfileSetsType($fset)::getRoot
  1116.     }
  1117.     if {[llength [info commands $cmd]] || [auto_load $cmd]} {
  1118.     set filename [fileset::$gfileSetsType($fset)::getRoot $fset]
  1119.     while {[file isdirectory $filename]} {
  1120.         set disp [list]
  1121.         foreach f [glob -dir $filename *] {
  1122.         lappend disp [file tail $f]
  1123.         }
  1124.         set disp [concat [list ..] [lsort -ignore $disp]]
  1125.         if {[catch {listpick -p {File?} $disp} choice]} {return}
  1126.         if {$choice == ".."} {
  1127.         set filename [file dirname $filename]
  1128.         } else {
  1129.         set filename [file join $filename $choice]
  1130.         }
  1131.     }
  1132.     edit $filename
  1133.     } else {
  1134.     set allfiles [getFilesInSet $fset]
  1135.     foreach f $allfiles {
  1136.         lappend disp [file tail $f]
  1137.     }
  1138.     if {[catch {listpick -l -p {File?} [lsort -ignore $disp]} files]} {return}
  1139.     foreach res $files {
  1140.         set ind [lsearch -glob $allfiles "\*${file::separator}[quote::Find $res]"]
  1141.         fileset::openItemProc $fset "" [lindex $allfiles $ind]
  1142.     }
  1143.     }
  1144. }
  1145.  
  1146. # We only return TEXT files, since we don't want Alpha
  1147. # manipulating the data fork of non-text files.
  1148. proc getFileSet {fset} {
  1149.     global filesetmodeVars
  1150.     if {$filesetmodeVars(includeNonTextFiles)} {
  1151.     set fnames ""
  1152.     foreach f [getFilesInSet $fset] {
  1153.         if {[file isfile $f]} {
  1154.         getFileInfo $f a
  1155.         if {$a(type) == "TEXT"} {
  1156.             lappend fnames $f
  1157.         }
  1158.         }
  1159.     }
  1160.     return $fnames
  1161.     } else {
  1162.     return [getFilesInSet $fset]
  1163.     }
  1164. }
  1165.  
  1166.